package com.jintian.smart.kernel.autoconfigure.beetl;

import com.jintian.smart.kernel.autoconfigure.ConfigConstants;
import com.jintian.smart.kernel.autoconfigure.extend.SnowGenerator;
import com.jintian.smart.kernel.autoconfigure.extend.SnowProperties;
import org.beetl.core.fun.ObjectUtil;
import org.beetl.sql.clazz.NameConversion;
import org.beetl.sql.clazz.kit.ClassLoaderKit;
import org.beetl.sql.core.IDAutoGen;
import org.beetl.sql.core.db.DBStyle;
import org.beetl.sql.core.loader.MarkdownClasspathLoader;
import org.beetl.sql.ext.spring.BeetlSqlClassPathScanner;
import org.beetl.sql.ext.spring.SqlManagerFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;

import java.util.*;
import java.util.Map.Entry;

/**
 * 创建SQLManager。Ordered.LOWEST_PRECEDENCE - 100 ，在ModelLoader前执行。
 */
public class BeetlSqlBeanDefinitionRegistrar implements BeanFactoryPostProcessor, BeanDefinitionRegistryPostProcessor, ResourceLoaderAware, EnvironmentAware, Ordered {
    public static final String BEANNAME_DEFAULT_SQMMANAGER = "sqlManager";
    ResourceLoader resourceLoader;
    Environment environment;
    BeanFactory beanFactory;
    SnowProperties snowProperties;

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
        SnowProperties snowProperties = new SnowProperties(environment);
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    private ClassLoader getClassLoader() {
        ClassLoader cl = this.resourceLoader.getClassLoader();
        if (cl != null)
            return cl;
        return Thread.currentThread().getContextClassLoader();
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        this.registerBeetlSqlBeanDefinitions(registry);
    }

    private void registerBeetlSqlBeanDefinitions(BeanDefinitionRegistry registry) {
        // 1.获取或创建BeetlSqlProperties
        BeetlSqlProperties beetlSqlProperties = BeetlSqlBeanUtils.getOrCreateBeetlSqlProperties(registry, (ConfigurableEnvironment) environment);

        // 2.创建SqlManager
        Map<String, BeetlSqlProperties.SQLManagerConfig> configs = beetlSqlProperties.getSqlManagers();
        String defaultSQLManager = null;
        for (Entry<String, BeetlSqlProperties.SQLManagerConfig> entry : configs.entrySet()) {
            String sqlManagerName = entry.getKey();
            BeetlSqlProperties.SQLManagerConfig config = entry.getValue();
            if (defaultSQLManager == null) {
                defaultSQLManager = sqlManagerName;
            }
            registerSQLManager(registry, sqlManagerName, config);
            if (config.getDynamic() == null) {
                config.withDefault(beetlSqlProperties.getCommon());
            }
        }

        registerConditionalSQLManager(registry, defaultSQLManager, new ArrayList<>(configs.keySet()));
    }

    protected void registerConditionalSQLManager(BeanDefinitionRegistry registry, String defaultSQLManager, List<String> all) {
        BeanDefinitionBuilder bdb = BeanDefinitionBuilder.rootBeanDefinition(ConditionalSQLManagerFactoryBean.class);
        bdb.addPropertyValue("all", all);
        bdb.addPropertyValue("name", BEANNAME_DEFAULT_SQMMANAGER);
        bdb.addPropertyValue("defaultSQLManager", defaultSQLManager);
        bdb.setPrimary(true);
        registry.registerBeanDefinition(BEANNAME_DEFAULT_SQMMANAGER, bdb.getBeanDefinition());
    }

    private void registerSQLManager(BeanDefinitionRegistry registry, String sqlManagerName, BeetlSqlProperties.SQLManagerConfig config) {
        if (!registry.containsBeanDefinition(sqlManagerName + "BeetlSqlDataSourceBean")) {
            BeanDefinitionBuilder sqlSourceBuilder = registerBeetlSqlSource(sqlManagerName, config);
            registry.registerBeanDefinition(sqlManagerName + "BeetlSqlDataSourceBean", sqlSourceBuilder.getBeanDefinition());
        }

        if (!registry.containsBeanDefinition(sqlManagerName)) {
            ClassLoader classloader = this.getClassLoader();

            MarkdownClasspathLoader loader = new MarkdownClasspathLoader(config.getSqlPath(), config.getSqlFileCharset());
            loader.setClassLoaderKit(new ClassLoaderKit(classloader));

            Properties ps = new Properties();
            ps.put("PRODUCT_MODE", config.getDev() ? "false" : "true");
            BeanDefinitionBuilder bdb = BeanDefinitionBuilder.rootBeanDefinition(SqlManagerFactoryBean.class);
            bdb.addPropertyValue("cs", new RuntimeBeanReference(sqlManagerName + "BeetlSqlDataSourceBean"));
            bdb.addPropertyValue("dbStyle", (DBStyle) ObjectUtil.tryInstance(config.getDbStyle(), classloader));
            bdb.addAutowiredProperty("interceptors");
            // bdb.addPropertyValue("interceptors", new Interceptor[]{new ConsoleDebugInterceptor()});
            bdb.addPropertyValue("dev", config.getDev());
            bdb.addPropertyValue("sqlLoader", loader);
            bdb.addPropertyValue("nc", (NameConversion) ObjectUtil.tryInstance(config.getNameConversion(), classloader));
            bdb.addPropertyValue("extProperties", ps);
            bdb.addPropertyValue("name", sqlManagerName);

            Map<String, IDAutoGen> autoGens = new HashMap<>();
            autoGens.put("snowid", new IDAutoGen() {
                @Override
                public Object nextID(String params) {
                    try {
                        long id = SnowGenerator.getInstance(snowProperties).getUID();
                        return String.valueOf(id);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            bdb.addPropertyValue("idAutoGens", autoGens);
            if (config.getBasePackage() != null) {
                BeetlSqlClassPathScanner scanner = new BeetlSqlClassPathScanner(registry);
                scanner.setResourceLoader(resourceLoader);
                scanner.setSqlManagerFactoryBeanName(sqlManagerName);
                scanner.setSuffix(config.getDaoSuffix());
                scanner.registerFilters();
                scanner.scan(config.getBasePackage().split(","));
            }

            BeanDefinition beanDefinition = bdb.getBeanDefinition();
            registry.registerBeanDefinition(sqlManagerName, beanDefinition);
        }
    }

    private BeanDefinitionBuilder registerBeetlSqlSource(String name, BeetlSqlProperties.SQLManagerConfig config) {
        String sourceConfig = config.getDs();
        String[] sources = sourceConfig.split(",");
        BeanDefinitionBuilder bdb = BeanDefinitionBuilder.rootBeanDefinition(BeetlSqlConnectionSourceFactory.class);
        bdb.addPropertyValue("masterSource", ConfigConstants.getDatasourceBeanName(sources[0]));
        if (sources.length == 1) {
            return bdb;
        }
        String[] slaves = new String[sources.length - 1];
        for (int i = 1; i < sources.length; i++) {
            slaves[i - 1] = ConfigConstants.getDatasourceBeanName(sources[i]);

        }
        bdb.addPropertyValue("slaveSource", slaves);
        return bdb;
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE - 100;
    }
}
